package com.xiashitech.agent.instrumentation.spring.refresh.api.inst;

import com.xiashitech.agent.cache.api.ApiCache;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.constants.ApiParamDataType;
import com.xiashitech.agent.constants.ErrorCode;
import com.xiashitech.agent.grpc.XsApiInfoClient;
import com.xiashitech.agent.instrumentation.log.XSLevel;
import com.xiashitech.agent.instrumentation.spring.controller.apicapture.inst.utils.GenerateParamsUtil;
import com.xiashitech.agent.utils.HttpUtil;
import com.xiashitech.agent.utils.JsonUtil;
import com.xiashitech.agent.utils.XiashiGrpcUtil;
import com.xiashitech.agent.utils.XSUtil;
import com.xiashitech.interfaces.agent.constants.XSConstants;
import com.xiashitech.interfaces.agent.dto.AgentConfigDTO;
import com.xiashitech.interfaces.agent.dto.api.*;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.common.AttributesBuilder;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.Logger;

import static java.util.logging.Level.*;

public class XSSpringMVCAllApiDeal {
    private static final Logger logger = Logger.getLogger(XSSpringMVCAllApiDeal.class.getName());
    private static final String TARGET_NAME_PREFIX = "scopedTarget.";
    private static final String PROXY_NAME_PREFIX = "com.sun.proxy";

    public static List<Api> extractApi(Object applicationContext) {
        List<Api> apis = new ArrayList();
        try {
            // long ts = System.nanoTime();
            String[] beanDefinitionNames = (String[]) applicationContext.getClass().getMethod("getBeanDefinitionNames").invoke(applicationContext);
//            Arrays.stream(beanDefinitionNames).filter(beanName -> !isScopedTarget(beanName)).map(beanName -> {
            Arrays.stream(beanDefinitionNames).map(beanName -> {
                // System.out.println(ts + " XSSpringMVCAllApiDeal 1 beanName: " + beanName);
                Class beanClass = null;
                if(isScopedTarget(beanName)) {
                    return beanClass;
                }
                try {
                    Object bean = applicationContext.getClass().getMethod("getBean", String.class).invoke(applicationContext, beanName);
                    // System.out.println(ts + " XSSpringMVCAllApiDeal 2 getBean: " + bean.getClass().getName());
                    beanClass = getOriginalBean(bean);
                    // System.out.println(ts + " XSSpringMVCAllApiDeal 3 beanClass: " + (beanClass==null?"NULL":beanClass.getName()));
                } catch (Throwable e) {
                    // StartConfig.throwableStackPrinter(e);
                    // System.out.println(ts + " XSSpringMVCAllApiDeal 4 exception: " + e.getMessage());
                }
                if (beanClass != null && beanClass.getName().startsWith(PROXY_NAME_PREFIX)) {
                    try {
                        return Class.forName(beanName);
                    } catch (ClassNotFoundException e) {
                        // StartConfig.throwableStackPrinter(e);
                    }
                }
                return beanClass;
//            }).filter(clazz -> clazz != null && !clazz.getName().startsWith("org.springframework")
//                            && Arrays.stream(clazz.getAnnotations()).anyMatch(annotation ->
//                            annotation.annotationType().getName().equals("org.springframework.stereotype.Controller")
//                                    || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RestController")
//                                    || annotation.annotationType().getName().equals("org.springframework.cloud.openfeign.FeignClient")
//                    )
            }).filter(clazz -> {
                if(clazz == null)
                    return false;
                if(clazz.getName().startsWith("org.springframework")) {
                    // System.out.println(ts + " XSSpringMVCAllApiDeal 5 spring className: " + clazz.getName());
                    return false;
                }
                Annotation[] annotations = clazz.getAnnotations();
                boolean isController = false;
                for(Annotation ann: annotations) {
                    // System.out.println(ts + " XSSpringMVCAllApiDeal 6 user className: " + clazz.getName() + ", annotationName: " + ann.annotationType().getName());
                    if(!isController && (ann.annotationType().getName().equals("org.springframework.stereotype.Controller")
                            || ann.annotationType().getName().equals("org.springframework.web.bind.annotation.RestController")
                            || ann.annotationType().getName().equals("org.springframework.cloud.openfeign.FeignClient"))) {
                        isController = true;
                    }
                }
                return isController;
            }).forEach(clazz -> {
                // System.out.println(ts + " XSSpringMVCAllApiDeal 7 real userClassName: " + clazz.getName());
                apis.addAll(generateApi(clazz));
            });
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return apis;
    }

    public static boolean isScopedTarget(String beanName) {
        return (beanName != null && beanName.startsWith(TARGET_NAME_PREFIX));
    }

    public static Boolean syncApiToOpenMetadata(List<Api> apis) {
        if (apis.size() == 0) {
            return Boolean.FALSE;
        }
        new Thread(() -> {
            try {
                ApiRequest apiRequest = ApiRequest.builder().systemId(StartConfig.getParamValueFromKey(StartConfig.systemId)).serviceName(InstrumentationConfig.get().getString("otel.service.name")).apis(apis).build();
                String param = JsonUtil.convertObjectToString(apiRequest);
                if (XiashiGrpcUtil.useGrpc()) {
                    XsApiInfoClient.getClient().pushApisToOdm(apiRequest);
                } else {
                    String response = HttpUtil.doPost(AgentConfigDTO.getConfig().getApiservice(), param, "application/json;charset=UTF-8");
                    if (response != null && !response.isEmpty() && JsonUtil.isJson(response)) {
                        ApiResponse apiResponse = JsonUtil.convertStringToObject(response, ApiResponse.class);
                        if (apiResponse.getCode() != null && apiResponse.getCode().equals(ErrorCode.RESULT_1001.getCode())) {
                            if (logger.isLoggable(FINE)) {
                                logger.log(FINE, "All apis has been synchronized to openmetadata, the apis is: {0}", JsonUtil.convertObjectToString(apis));
                            } else {
                                if (InstrumentationConfig.get().getBoolean("xs.debug", false)) {
                                    logger.log(XSLevel.DEBUG, "All apis has been synchronized to openmetadata");
                                } else {
                                    logger.log(INFO, "All apis has been synchronized to openmetadata");
                                }
                            }
                        } else {
                            if (InstrumentationConfig.get().getBoolean("xs.debug", false)) {
                                logger.log(XSLevel.DEBUG, "All apis(or some apis) has not been synchronized to openmetadata");
                            } else {
                                logger.log(WARNING, "All apis(or some apis) has not been synchronized to openmetadata");
                            }
                        }
                    } else {
                        if (InstrumentationConfig.get().getBoolean("xs.debug", false)) {
                            logger.log(XSLevel.DEBUG, "All apis(or some apis) has not been synchronized to openmetadata");
                        } else {
                            logger.log(WARNING, "All apis(or some apis) has not been synchronized to openmetadata");
                        }
                    }
                }
            } catch (Throwable e) {
                logger.log(XSLevel.DEBUG, "The Agent fails to transfer api information to Agentservice.");
            }
        }).start();
        return Boolean.TRUE;
    }

    public static void syncApiToLocalCache(List<Api> apis) {
        if (apis.size() == 0) {
            return;
        }
        new Thread(() -> {
            try {
                ApiCache.addBatch(apis);
                XSUtil.debug("All apis({}) has been stored into local cache.", apis.size());
            } catch (Throwable e) {
                XSUtil.debug("All apis({}) cached ERROR.", apis.size());
                StartConfig.throwableStackPrinter(e);
            }
        }).start();
    }

    public static void generateApisSpanAttr(List<Api> apis, AttributesBuilder attributesBuilder) {
        attributesBuilder.put("xs.allApiXs.size", apis.size());
        if (apis.size() > 0) {
            attributesBuilder.put("xs.allApi", JsonUtil.convertObjectToString(apis));
            attributesBuilder.put("xs.systemId", StartConfig.getParamValueFromKey(StartConfig.systemId));
            attributesBuilder.put("xs.serviceName", InstrumentationConfig.get().getString("otel.service.name"));
            attributesBuilder.put("xs.requestDate", String.valueOf(new Date().getTime()));
        }
    }

    /**
     * TODO 当使用接口上注解的方式，当前只扫接口上方法；尚没考虑扩展时如何取；
     */
    private static List<Api> generateApi(Class clazz) {
        List<Api> apis = new ArrayList();
        List<HttpMethod> httpMethod = new ArrayList();
        List<String> classPaths = new ArrayList();
        List<Consumes> consumes = new ArrayList();
        List<Produces> produces = new ArrayList();
        long reqId = System.nanoTime();
        // System.out.println(reqId + " XSSpringMVCAllApiDeal generateApi clazz A: " + clazz.getName() + ", " + System.identityHashCode(clazz));
        /*
         * 处理controller类上RequestMapping注解
         */
        // 为支持银联项目 Controller 实现接口，且注解放在接口上的情况，同时兼容 skywalking 通过添加接口方式增强 Controller
        Optional<Annotation> controllerAnnotation = Optional.empty();
        Optional<Class> interfaceOpt = Arrays.stream(clazz.getInterfaces()).findFirst();
        if (interfaceOpt.isPresent()){
            Class interfaceClass = interfaceOpt.get();
            controllerAnnotation = Arrays.stream(interfaceClass.getAnnotations()).filter(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestMapping")).findFirst();
        }
        if(controllerAnnotation.isPresent()) { // 表示有接口且接口上有注解
            clazz = interfaceOpt.get();
        } else {
            controllerAnnotation = Arrays.stream(clazz.getAnnotations()).filter(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestMapping")).findFirst();
        }

        // Optional<Annotation> controllerAnnotation = Arrays.stream(clazz.getAnnotations()).filter(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestMapping")).findFirst();
        if (controllerAnnotation.isPresent()) {
            try {
                Object requestMapping = controllerAnnotation.get();
                Object[] methods = (Object[]) requestMapping.getClass().getMethod("method").invoke(requestMapping);
                Arrays.stream(methods).forEach(requestMethod -> {
                    try {
                        String name = (String) requestMethod.getClass().getMethod("name").invoke(requestMethod);
                        httpMethod.add(HttpMethod.builder().name(name).build());
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                });

                // 兼容 3.1 之前的版本 没有 consumes produces
                try {
                    String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                    Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                        consumes.add(Consumes.builder().name(requestConsumes).build());
                    });
                    String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                    Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                        produces.add(Produces.builder().name(responseProduces).build());
                    });
                } catch (NoSuchMethodException e) {
                }

                String[] requestMappingValues = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                if (requestMappingValues.length == 0) {
                    try {
                        String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                        if (requestMappingPaths.length > 0) {
                            classPaths.addAll(Arrays.asList(requestMappingPaths));
                        }
                    } catch (Throwable e) {
                    }
                } else {
                    classPaths.addAll(Arrays.asList(requestMappingValues));
                }
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
        }
        /**
         * 处理controller的ResponseBody注解
         */
        Optional<Annotation> responseBodyAnnotation = Arrays.stream(clazz.getAnnotations()).filter(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.ResponseBody") || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RestController")).findFirst();
        if (responseBodyAnnotation.isPresent()) {
            produces.add(Produces.builder().name("application/json").build());
        }
        /*
         * controller方法注解解析
         */
        Arrays.stream(clazz.getDeclaredMethods()).filter(method -> Modifier.isPublic(method.getModifiers()) && Arrays.stream(method.getAnnotations()).anyMatch(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestMapping") ||
                annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.PutMapping") ||
                annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.DeleteMapping") ||
                annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.GetMapping") ||
                annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.PatchMapping") ||
                annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.PostMapping"))).forEach(method -> {
            List<Path> paths = new ArrayList();
            List<String> methodPaths = new ArrayList();
            List<Params> requestParams = new ArrayList();
            List<Params> responseParams = new ArrayList();
            List<HttpMethod> httpMethodForMethod = new ArrayList();
            httpMethodForMethod.addAll(httpMethod);
            List<Consumes> consumesForMethod = new ArrayList();
            consumesForMethod.addAll(consumes);
            List<Produces> producesForMethod = new ArrayList();
            producesForMethod.addAll(produces);
            /*
             * 处理http method和请求path，以及consumes和produces
             */
            Arrays.stream(method.getAnnotations()).forEach(annotation -> {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType.getName().equals("org.springframework.web.bind.annotation.RequestMapping")) {
                    try {
                        Object requestMapping = annotation;
                        Object[] methods = (Object[]) requestMapping.getClass().getMethod("method").invoke(requestMapping);
                        Arrays.stream(methods).forEach(requestMethod -> {
                            try {
                                String name = (String) requestMethod.getClass().getMethod("name").invoke(requestMethod);
                                if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals(name))) {
                                    httpMethodForMethod.add(HttpMethod.builder().name(name).build());
                                }
                            } catch (Throwable e) {
                                StartConfig.throwableStackPrinter(e);
                            }
                        });
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }

                        // 兼容 3.1 之前的版本 没有 consumes produces
                        try {
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                            String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                            Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                                producesForMethod.add(Produces.builder().name(responseProduces).build());
                            });
                        } catch (NoSuchMethodException e) {
                        }

                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                } else if (annotationType.getName().equals("org.springframework.web.bind.annotation.DeleteMapping")) {
                    try {
                        Object requestMapping = annotation;
                        if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals("DELETE"))) {
                            httpMethodForMethod.add(HttpMethod.builder().name("DELETE").build());
                        }
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }

                        // 兼容 3.1 之前的版本 没有 consumes produces
                        try {
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                            String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                            Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                                producesForMethod.add(Produces.builder().name(responseProduces).build());
                            });
                        } catch (NoSuchMethodException e) {
                        }
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                } else if (annotationType.getName().equals("org.springframework.web.bind.annotation.GetMapping")) {
                    try {
                        Object requestMapping = annotation;
                        if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals("GET"))) {
                            httpMethodForMethod.add(HttpMethod.builder().name("GET").build());
                        }
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }
                        try { // 对于低版本，< 4.3.5 版本，没有 cosumes 方法，就不取这信息
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                        } catch (Exception e) {

                        }
                        String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                        Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                            producesForMethod.add(Produces.builder().name(responseProduces).build());
                        });
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                } else if (annotationType.getName().equals("org.springframework.web.bind.annotation.PatchMapping")) {
                    try {
                        Object requestMapping = annotation;
                        if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals("PATCH"))) {
                            httpMethodForMethod.add(HttpMethod.builder().name("PATCH").build());
                        }
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }
                        try {
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                            String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                            Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                                producesForMethod.add(Produces.builder().name(responseProduces).build());
                            });
                        } catch (NoSuchMethodException e) {
                        }
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                } else if (annotationType.getName().equals("org.springframework.web.bind.annotation.PostMapping")) {
                    try {
                        Object requestMapping = annotation;
                        if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals("POST"))) {
                            httpMethodForMethod.add(HttpMethod.builder().name("POST").build());
                        }
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }
                        try {
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                            String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                            Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                                producesForMethod.add(Produces.builder().name(responseProduces).build());
                            });
                        } catch (NoSuchMethodException e) {
                        }
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                } else if (annotationType.getName().equals("org.springframework.web.bind.annotation.PutMapping")) {
                    try {
                        Object requestMapping = annotation;
                        if (!httpMethodForMethod.stream().anyMatch(existHttpMethod -> existHttpMethod.getName().equals("PUT"))) {
                            httpMethodForMethod.add(HttpMethod.builder().name("PUT").build());
                        }
                        String[] values = (String[]) requestMapping.getClass().getMethod("value").invoke(requestMapping);
                        if (values.length == 0) {
                            try {
                                String[] requestMappingPaths = (String[]) requestMapping.getClass().getMethod("path").invoke(requestMapping);
                                if (requestMappingPaths.length > 0) {
                                    methodPaths.addAll(Arrays.asList(requestMappingPaths));
                                }
                            } catch (Throwable e) {
                            }
                        } else {
                            methodPaths.addAll(Arrays.asList(values));
                        }
                        try {
                            String[] requestMappingConsumes = (String[]) requestMapping.getClass().getMethod("consumes").invoke(requestMapping);
                            Arrays.stream(requestMappingConsumes).forEach(requestConsumes -> {
                                consumesForMethod.add(Consumes.builder().name(requestConsumes).build());
                            });
                            String[] requestMappingProduces = (String[]) requestMapping.getClass().getMethod("produces").invoke(requestMapping);
                            Arrays.stream(requestMappingProduces).forEach(responseProduces -> {
                                producesForMethod.add(Produces.builder().name(responseProduces).build());
                            });
                        } catch (NoSuchMethodException e) {
                        }
                    } catch (Throwable e) {
                        StartConfig.throwableStackPrinter(e);
                    }
                }
                if (annotationType.getName().equals("org.springframework.web.bind.annotation.ResponseBody")) {
                    if (!producesForMethod.stream().anyMatch(producesForResponseBody -> producesForResponseBody.getName().toLowerCase().contains("json"))) {
                        producesForMethod.add(Produces.builder().name("application/json").build());
                    }
                }
            });

            /*
             * 处理请求参数
             */
            Arrays.stream(method.getParameters()).forEach(methodParameter -> {
                if (methodParameter.getType().getName().equals("javax.servlet.http.HttpServletRequest") ||
                        methodParameter.getType().getName().equals("javax.servlet.http.HttpServletResponse") ||
                        methodParameter.getType().getName().equals("org.springframework.ui.Model")) {
                    return;
                }
                if (methodParameter.getDeclaredAnnotations().length > 0) {
                    if (Arrays.stream(methodParameter.getDeclaredAnnotations()).anyMatch(annotation -> annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestParam")
                            || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.PathVariable") || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.CookieValue")
                            || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.MatrixVariable") || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestBody")
                            || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestHeader") || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestPart")
                            || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestAttribute") || annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.SessionAttribute"))) {
                        Arrays.stream(methodParameter.getDeclaredAnnotations()).forEach(annotation -> {
                            try {
                                if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestParam")) {
                                    Object requestParam = annotation;
                                    String paramName = "";
                                    String value = (String) requestParam.getClass().getMethod("value").invoke(requestParam);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) requestParam.getClass().getMethod("name").invoke(requestParam);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.PathVariable")) {
                                    Object pathVariable = annotation;
                                    String paramName = "";
                                    String value = (String) pathVariable.getClass().getMethod("value").invoke(pathVariable);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) pathVariable.getClass().getMethod("name").invoke(pathVariable);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.CookieValue")) {
                                    Object cookieValue = annotation;
                                    String paramName = "";
                                    String value = (String) cookieValue.getClass().getMethod("value").invoke(cookieValue);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) cookieValue.getClass().getMethod("name").invoke(cookieValue);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("cookie").build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("cookie").build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.MatrixVariable")) {
                                    Object matrixVariable = annotation;
                                    String paramName = "";
                                    String value = (String) matrixVariable.getClass().getMethod("value").invoke(matrixVariable);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) matrixVariable.getClass().getMethod("name").invoke(matrixVariable);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    String pathVar = (String) matrixVariable.getClass().getMethod("pathVar").invoke(matrixVariable);
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).isMatrixVariable(Boolean.TRUE).pathVar(pathVar).build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).isMatrixVariable(Boolean.TRUE).pathVar(pathVar).build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestAttribute")) {
                                    Object requestAttribute = annotation;
                                    String paramName = "";
                                    String value = (String) requestAttribute.getClass().getMethod("value").invoke(requestAttribute);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) requestAttribute.getClass().getMethod("name").invoke(requestAttribute);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("requestAttribute").build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("requestAttribute").build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestBody")) {
                                    if (!consumesForMethod.stream().anyMatch(consumesForRequestBody -> consumesForRequestBody.getName().toLowerCase().contains("json"))) {
                                        consumesForMethod.add(Consumes.builder().name("application/json").build());
                                    }
                                    if (Collection.class.isAssignableFrom(methodParameter.getType())) {
                                        Type genericType = methodParameter.getParameterizedType();
                                        if (genericType != null && genericType instanceof ParameterizedType) {
                                            ParameterizedType parameterizedType = (ParameterizedType) genericType;
                                            for (Type actualType : parameterizedType.getActualTypeArguments()) {
                                                if (actualType instanceof Class) {
                                                    Class clazzActualType = (Class) actualType;
                                                    if (GenerateParamsUtil.isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).subParams(Arrays.asList(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
                                                    } else {
                                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).subParams(GenerateParamsUtil.generateParams(clazzActualType)).build());
                                                        GenerateParamsUtil.clearCompletedClass();
                                                    }
                                                }
                                            }
                                        } else {
                                            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                        }
                                    } else {
                                        requestParams.addAll(GenerateParamsUtil.generateParams(methodParameter.getType()));
                                        GenerateParamsUtil.clearCompletedClass();
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestHeader")) {
                                    Object requestHeader = annotation;
                                    String paramName = "";
                                    String value = (String) requestHeader.getClass().getMethod("value").invoke(requestHeader);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) requestHeader.getClass().getMethod("name").invoke(requestHeader);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("requestHeader").build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("requestHeader").build());
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.RequestPart")) {
                                    Object requestPart = annotation;
                                    String paramName = "";
                                    String value = (String) requestPart.getClass().getMethod("value").invoke(requestPart);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) requestPart.getClass().getMethod("name").invoke(requestPart);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (methodParameter.getType().getName().equals("org.springframework.web.multipart.MultipartFile")) {
                                        if (!"".equals(paramName)) {
                                            requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                        } else {
                                            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
                                        }
                                    } else {
                                        if (!"".equals(paramName)) {
                                            requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.OBJECT.value()).subParams(GenerateParamsUtil.generateParams(methodParameter.getType())).build());
                                            GenerateParamsUtil.clearCompletedClass();
                                        } else {
                                            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.OBJECT.value()).subParams(GenerateParamsUtil.generateParams(methodParameter.getType())).build());
                                            GenerateParamsUtil.clearCompletedClass();
                                        }
                                    }
                                } else if (annotation.annotationType().getName().equals("org.springframework.web.bind.annotation.SessionAttribute")) {
                                    Object sessionAttribute = annotation;
                                    String paramName = "";
                                    String value = (String) sessionAttribute.getClass().getMethod("value").invoke(sessionAttribute);
                                    if (value.isEmpty()) {
                                        try {
                                            String name = (String) sessionAttribute.getClass().getMethod("name").invoke(sessionAttribute);
                                            paramName = name;
                                        } catch (Throwable e) {
                                        }
                                    } else {
                                        paramName = value;
                                    }
                                    if (!"".equals(paramName)) {
                                        requestParams.add(Params.builder().name(paramName).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("sessionAttribute").build());
                                    } else {
                                        requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).specialSource("sessionAttribute").build());
                                    }
                                }
                            } catch (Throwable e) {
                                StartConfig.throwableStackPrinter(e);
                            }
                        });
                    } else {
                        dealMethodRequestParameter(methodParameter, requestParams);
                    }
                } else {
                    dealMethodRequestParameter(methodParameter, requestParams);
                }
            });
            if (consumesForMethod.size() == 0) {
                consumesForMethod.add(Consumes.builder().name("application/x-www-form-urlencoded").build());
            }
            if (producesForMethod.size() == 0) {
                producesForMethod.add(Produces.builder().name("application/x-www-form-urlencoded").build());
            }
            /*
             * 处理返回值
             */
            Class methodParameter = method.getReturnType();
            if (GenerateParamsUtil.isPrimitiveOrWrap(methodParameter) || ApiParamDataType.judgeAnalogyPrimitive(methodParameter.getSimpleName())) {
                if (!methodParameter.getName().equals("void")) {
                    responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(methodParameter.getSimpleName())).build());
                }
            } else if ("com.alibaba.fastjson.JSONObject".endsWith(methodParameter.getName())) {
                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.JSON_OBJECT.value()).build());
            } else if ("com.alibaba.fastjson.JSONArray".endsWith(methodParameter.getName())) {
                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.JSON_ARRAY.value()).build());
            } else if (Collection.class.isAssignableFrom(methodParameter)) {
                Type genericType = method.getGenericReturnType();
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) genericType;
                    for (Type actualType : parameterizedType.getActualTypeArguments()) {
                        if (actualType instanceof Class) {
                            Class clazzActualType = (Class) actualType;
                            if (GenerateParamsUtil.isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(methodParameter.getSimpleName())).subParams(Arrays.asList(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
                            } else {
                                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(methodParameter.getSimpleName())).subParams(GenerateParamsUtil.generateParams(clazzActualType)).build());
                                GenerateParamsUtil.clearCompletedClass();
                            }
                        }
                    }
                } else {
                    responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(methodParameter.getSimpleName())).build());
                }
            } else if (methodParameter.isArray()) {
                Class clazzActualType = methodParameter.getComponentType();
                if (GenerateParamsUtil.isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                    responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.ARRAY.value()).subParams(Arrays.asList(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
                } else {
                    responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.ARRAY.value()).subParams(GenerateParamsUtil.generateParams(clazzActualType)).build());
                    GenerateParamsUtil.clearCompletedClass();
                }
            } else if (Map.class.isAssignableFrom(methodParameter)) {
                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.MAP.value()).build());
            } else if (methodParameter == Object.class) {
                responseParams.add(Params.builder().name(methodParameter.getSimpleName().toLowerCase()).type(ApiParamDataType.OBJECT.value()).build());
            } else {
                responseParams.addAll(GenerateParamsUtil.generateParams(methodParameter));
                GenerateParamsUtil.clearCompletedClass();
            }

            /*
             * 处理path
             */
            if (classPaths.size() > 0) {
                classPaths.forEach(classPath -> {
                    if (methodPaths.size() > 0) {
                        methodPaths.forEach(methodPath -> {
                            paths.add(Path.builder().name(classPath + methodPath).build());
                        });
                    } else {
                        paths.add(Path.builder().name(classPath).build());
                    }
                });
            } else {
                if (methodPaths.size() > 0) {
                    methodPaths.forEach(methodPath -> {
                        paths.add(Path.builder().name(methodPath).build());
                    });
                }
            }
            if (httpMethodForMethod.size() == 0) {
                httpMethodForMethod.add(HttpMethod.builder().name("GET").build());
                httpMethodForMethod.add(HttpMethod.builder().name("HEAD").build());
                httpMethodForMethod.add(HttpMethod.builder().name("POST").build());
                httpMethodForMethod.add(HttpMethod.builder().name("PUT").build());
                httpMethodForMethod.add(HttpMethod.builder().name("PATCH").build());
                httpMethodForMethod.add(HttpMethod.builder().name("DELETE").build());
                httpMethodForMethod.add(HttpMethod.builder().name("OPTIONS").build());
                httpMethodForMethod.add(HttpMethod.builder().name("TRACE").build());
            }
            removeDuplicate(requestParams, null);
            removeDuplicate(responseParams, null);

            // 没有必要做动态区分，目前词版本拦截入口锁做的全量扫描全部都是归类到 ApiType.RESTFUL。
            // 之后新增的其它分类下的拦截入口如果是其他分类则到时候直接归到其他类别即可
            String apiType = XSConstants.ApiType.RESTFUL;
            apis.add(
                    Api.builder().paths(paths).consumes(consumesForMethod).produces(producesForMethod).apiType(apiType)
                            .httpMethods(httpMethodForMethod).requestParams(requestParams).responseParams(responseParams)
                            .build()
            );
        });
        return apis;
    }

    private static void removeDuplicate(List<Params> paramList, Params param) {
        try {
            if (paramList == null || paramList.size() == 0) {
                return;
            }
            Set<Params> duplicate = new HashSet();
            Set<String> existKey = new HashSet();
            List<Params> results = new ArrayList();
            paramList.forEach(paramItem -> {
                if (!existKey.contains(paramItem.getName())) {
                    existKey.add(paramItem.getName());
                    results.add(paramItem);
                } else {
                    duplicate.add(paramItem);
                }
            });
            if (param != null) {
                param.setSubParams(results);
            } else {
                duplicate.forEach(duplicateItem -> paramList.remove(duplicateItem));
            }
            paramList.forEach(paramItem -> removeDuplicate(paramItem.getSubParams(), paramItem));
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
    }

    private static void dealMethodRequestParameter(Parameter methodParameter, List<Params> requestParams) {
        if (GenerateParamsUtil.isPrimitiveOrWrap(methodParameter.getType()) || ApiParamDataType.judgeAnalogyPrimitive(methodParameter.getType().getSimpleName())) {
            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
        } else if ("com.alibaba.fastjson.JSONObject".endsWith(methodParameter.getType().getName())) {
            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.JSON_OBJECT.value()).build());
        } else if ("com.alibaba.fastjson.JSONArray".endsWith(methodParameter.getType().getName())) {
            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.JSON_ARRAY.value()).build());
        } else if (Collection.class.isAssignableFrom(methodParameter.getType())) {
            Type genericType = methodParameter.getParameterizedType();
            if (genericType != null && genericType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                for (Type actualType : parameterizedType.getActualTypeArguments()) {
                    if (actualType instanceof Class) {
                        Class clazzActualType = (Class) actualType;
                        if (GenerateParamsUtil.isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).subParams(Arrays.asList(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
                        } else {
                            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).subParams(GenerateParamsUtil.generateParams(clazzActualType)).build());
                            GenerateParamsUtil.clearCompletedClass();
                        }
                    }
                }
            } else {
                requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(methodParameter.getType().getSimpleName())).build());
            }
        } else if (methodParameter.getType().isArray()) {
            Class clazzActualType = methodParameter.getType().getComponentType();
            if (GenerateParamsUtil.isPrimitiveOrWrap(clazzActualType) || ApiParamDataType.judgeAnalogyPrimitive(clazzActualType.getSimpleName())) {
                requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.ARRAY.value()).subParams(Arrays.asList(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.fromValue(clazzActualType.getSimpleName())).build())).build());
            } else {
                requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.ARRAY.value()).subParams(GenerateParamsUtil.generateParams(clazzActualType)).build());
                GenerateParamsUtil.clearCompletedClass();
            }
        } else if (Map.class.isAssignableFrom(methodParameter.getType())) {
            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.MAP.value()).build());
        } else if (methodParameter.getType() == Object.class) {
            requestParams.add(Params.builder().name(methodParameter.getName()).type(ApiParamDataType.OBJECT.value()).build());
        } else {
            requestParams.addAll(GenerateParamsUtil.generateParams(methodParameter.getType()));
            GenerateParamsUtil.clearCompletedClass();
        }
    }

    private static Class getOriginalBean(Object proxyClass) {
        Class originalClass = proxyClass.getClass();
        try {
            Class aopUtil = Class.forName("org.springframework.aop.support.AopUtils");
            Method isAopProxy = aopUtil.getMethod("isAopProxy", Object.class);
            if ((Boolean) isAopProxy.invoke(null, proxyClass)) {
                Method getTargetClass = aopUtil.getMethod("getTargetClass", Object.class);
                originalClass = (Class) getTargetClass.invoke(null, proxyClass);
            }
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return originalClass;
    }
}